package seismosurfer.servlets.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;

import seismosurfer.data.UserData;
import seismosurfer.util.Assert;
import seismosurfer.util.Registry;

import com.bbn.openmap.util.Debug;

/**
 * The SessionHandler allows thread-safe access to the current sessions of the
 * current context. The session objects that are cached by this service are
 * obtained through a listener, which must be configured via your web
 * application's <code>web.xml</code> deployment descriptor as follows:
 * 
 * <blockquote><code><pre>
 *   &lt;listener&gt;
 *     &lt;listener-class&gt;
 *       seismosurfer.servlets.util.SessionListener
 *     &lt;/listener-class&gt;
 *   &lt;/listener&gt;
 * </pre></code></blockquote>
 * 
 * Borrowed from Turbine.
 */
public class SessionHandler {

    protected static final String USERS_COUNT = "usersCount";

    protected static final String KEY = "key";

    /** Map of active sessions */
    private Map activeSessions = new HashMap();

    private ServletContext context;

    private static SessionHandler handler = new SessionHandler();

    protected SessionHandler() {
    }

    public static SessionHandler getInstance() {
        return handler;
    }
    
    /**
     * Sets the ServletContext.
     * This is called only by the listener.
     * 
     * @param ctx the ServletContext object
     */    
    public static void setServletContext(ServletContext ctx) {
        getInstance().context = ctx;
    }
    
    /**
     * This is called only by the listener.
     * 
     * @return the ServletContext object
     */    
    public static ServletContext getServletContext() {
        return getInstance().context;
    }
    
    /**
     * Sets the Google Maps key in the {@link #KEY}
     * servlet context attribute.
     *
     */
    public static void setGoogleMapsKey() {
        Properties p = Registry.getOpenmapProperties();
        String key = p.getProperty("seismosurfer.maps.key");
        Assert.notNull(key);

        getServletContext().setAttribute(KEY, key);
    }

    /**
     * Gets a list of the active sessions.
     * 
     * @return A copy of the list of <code>HttpSession</code> objects.
     */
    public static Collection getActiveSessions() {
        // Sync externally to allow ArrayList's ctor to iterate
        // activeSessions' values in a thread-safe fashion.
        synchronized (getInstance().activeSessions) {
            return new ArrayList(getInstance().activeSessions.values());
        }
    }
    
    /**
     * Gets the number of users who
     * are associated with a session.
     * 
     * @return the number of users
     */
    public static int getUsersCount() {
        return getInstance().activeSessions.size();
    }

    /**
     * Gets a list users who are associated with a session.
     * 
     * @return a list of UserData objects
     */
    public static List getUsers() {
        List users = new ArrayList();

        Collection sessions = getActiveSessions();

        for (Iterator iter = sessions.iterator(); iter.hasNext();) {
            HttpSession session = (HttpSession) iter.next();

            UserData user = (UserData) session.getAttribute(UserData.USER);

            users.add(user);
        }

        return users;
    }

    /**
     * Adds a session to the current list. This method should only be called by
     * the listener.
     * 
     * @param session
     *            Session to add
     */
    public static void addSession(HttpSession session) {
        getInstance().activeSessions.put(session.getId(), session);

        Debug.output("Adding a session...Sessions #:" + getUsersCount());

        updateUsersCount();
    }

    /**
     * Removes a session from the current list. This method should only be
     * called by the listener.
     * 
     * @param session
     *            Session to remove
     */
    public static void removeSession(HttpSession session) {
        getInstance().activeSessions.remove(session.getId());
        Debug.output("Removing a session...Sessions #:" + getUsersCount());
        updateUsersCount();
    }

    private static void updateUsersCount() {
        getInstance().context.setAttribute(USERS_COUNT, new Integer(
                getUsersCount()));
        Debug.output("Users #:" + getUsersCount());
    }   
    
    
    /**
     * Gets the HttpSession by the session identifier
     * 
     * @param sessionId
     *            The unique session identifier.
     * @return The session keyed by the specified identifier.
     */
    public static HttpSession getSession(String sessionId) {
        return (HttpSession) getInstance().activeSessions.get(sessionId);
    }    
}